﻿#ifndef TL_GRAPH_H
#define TL_GRAPH_H

#include <iostream>
#include "TL_Matrix.h"
#include "TL_Status.h"
#include "TL_Vector.h"


/*
КОДЫ ОШИБОК:
1 - неизвестная ошибка
2 - вершина с таким именем уже существует
3 - невозможно добавить строку в матрицу смежности
4 - невозможно добавить столбец в матрицу смежности
5 - невозможно добавить значение в вектор имен
6 - невозможно пересоздать матрицу смежности
7 - вершина с таким именем НЕ существует
8 - невозможно добавить значение в матрицу смежности
9 - невозможно создать вектор ребер
10 - невозможно получить значение из матрицы смежности
11 - невозможно провести ребро из вершину в эту же вершину
12 - невозможно удалить ребро
13 - невозможно удалить строку из матрицы смежности
14 - невозможно удалить столбец из матрицы смежности
15 - невозможно удалить значение из вектора имен
16 - вершина с таким номером НЕ существует
*/


namespace TL {
    /* TL::Graph */

    template<class T, class W> class Graph { // типы: T - имя вершины, W - вес вершины
    private:
        TL::Matrix<W> m_AdjacencyMatrix; // матрица смежности
        TL::Vector<T> m_VertexNames; // вектор имен вершин

    private:
        unsigned int GetVertexNumber(T vertexName);

    public:
        // TODO: constructors, etc

    public:
        void AddEdge(T vertexFromName, T vertexToName, W newEdgeWeight, TL::Status *status);
        void AddVertex(T vertexName, TL::Status *status);
        TL::Vector<unsigned int> GetEdges(T vertexName, TL::Status *status); // вектор с номерами вершин, в которые проведены ребра
        W GetEdgeWeight(T vertexFromName, T vertexToName, TL::Status *status);
        T GetVertexName(unsigned int vertexNumber, TL::Status *status);
        unsigned int GetVertexNumber(T vertexName, TL::Status *status);
        bool IsExisted(T vertexName);
        void RemoveEdge(T vertexFromName, T vertexToName, TL::Status *status);
        void RemoveVertex(T vertexName, TL::Status *status);
        unsigned int Size();

    public:
        template<class T, class W> friend std::ostream &operator<<(std::ostream &stream, Graph<T, W> &graph);
    };

    template<class T, class W> unsigned int Graph<T, W>::GetVertexNumber(T vertexName)
    {
        for(unsigned int i = 0; i < m_VertexNames.Size(); i++) {
            if(m_VertexNames[i] == vertexName)
                return i;
        }

        return ((unsigned int) ~ ((unsigned int)0)); // took from basetsd.h
    }

    template<class T, class W> void Graph<T, W>::AddEdge(T vertexFromName, T vertexToName, W newEdgeWeight, TL::Status *status)
    {
        status->SetNull();

        if(!IsExisted(vertexFromName) || !IsExisted(vertexToName)) {
            status->SetError(7);
            return;
        }

        if(vertexFromName == vertexToName) {
            status->SetError(11);
            return;
        }
    
        m_AdjacencyMatrix.Set(GetVertexNumber(vertexFromName), GetVertexNumber(vertexToName), newEdgeWeight, status);

        if(!status->m_Error)
            status->SetSuccess();
        else
            status->SetError(8);
    }

    template<class T, class W> void Graph<T, W>::AddVertex(T vertexName, TL::Status *status)
    {
        status->SetNull();

        if(IsExisted(vertexName)) {
            status->SetError(2);
            return;
        }

        unsigned int newVertexNumber = m_AdjacencyMatrix.GetRowCount();

        if(m_AdjacencyMatrix.IsCreated()) {
            m_AdjacencyMatrix.AddRow(newVertexNumber, status);

            if(status->m_Error) {
                status->SetError(3);
                return;
            }

            m_AdjacencyMatrix.AddColumn(newVertexNumber, status);

            if(status->m_Error) {
                status->SetError(4);
                return;
            }
        } else {
            m_AdjacencyMatrix.Create(1, 1, status);

            if(status->m_Error) {
                status->SetError(6);
                return;
            }
        }

        m_VertexNames.Add(newVertexNumber, vertexName, status);

        if(status->m_Error) {
            status->SetError(5);
            return;
        }

        status->SetSuccess();
    }

    template<class T, class W> TL::Vector<unsigned int> Graph<T, W>::GetEdges(T vertexName, TL::Status *status)
    {
        status->SetNull();

        TL::Vector<unsigned int> vec;

        if(!IsExisted(vertexName)) {
            status->SetError(7);
            return vec;
        }

        unsigned int vertexNumber = GetVertexNumber(vertexName);
    
        for(unsigned int i = 0; i < m_AdjacencyMatrix.GetColumnCount(); i++) {
            if(m_AdjacencyMatrix.IsDefined(vertexNumber, i, status))
                vec.Add(vec.Size(), i, status);

            if(status->m_Error) {
                status->SetError(9);
                return vec;
            }
        }

        status->SetSuccess();

        return vec;
    }

    template<class T, class W> W Graph<T, W>::GetEdgeWeight(T vertexFromName, T vertexToName, TL::Status *status)
    {
        status->SetNull();

        if(!IsExisted(vertexFromName) || !IsExisted(vertexToName)) {
            status->SetError(7);
            return 0;
        }

        unsigned int vertexFromNumber = GetVertexNumber(vertexFromName);
        unsigned int vertexToNumber = GetVertexNumber(vertexToName);

        if(vertexFromNumber == vertexToNumber) {
            status->SetError(10);
            return 0;
        }

        if(!m_AdjacencyMatrix.IsDefined(vertexFromNumber, vertexToNumber, status)) {
            status->SetError(10);
            return 0;
        }

        // success status is set in matrix's Get() method
        //status->SetSuccess();

        return m_AdjacencyMatrix.Get(vertexFromNumber, vertexToNumber, status);
    }

    template<class T, class W> T Graph<T, W>::GetVertexName(unsigned int vertexNumber, TL::Status *status)
    {
        status->SetNull();

        if(vertexNumber >= this->Size()) {
            status->SetError(16);
            return 0;
        }

        status->SetSuccess();

        return m_VertexNames[vertexNumber];
    }

    template<class T, class W> unsigned int Graph<T, W>::GetVertexNumber(T vertexName, TL::Status *status)
    {
        status->SetNull();

        if(!IsExisted(vertexName)) {
            status->SetError(7);
            return 0;
        }

        for(unsigned int i = 0; i < m_VertexNames.Size(); i++) {
            if(m_VertexNames[i] == vertexName) {
                status->SetSuccess();
                return i;
            }
        }

        return 0; // на всякий случай (мы никогда не должны дойти до этого return)
    }

    template<class T, class W> bool Graph<T, W>::IsExisted(T vertexName)
    {
        for(unsigned int i = 0; i < m_VertexNames.Size(); i++) {
            if(m_VertexNames[i] == vertexName)
                return true;
        }

        return false;
    }

    template<class T, class W> void Graph<T, W>::RemoveEdge(T vertexFromName, T vertexToName, TL::Status *status)
    {
        status->SetNull();

        if(!IsExisted(vertexFromName) || !IsExisted(vertexToName)) {
            status->SetError(7);
            return;
        }

        /*if(vertexFromName == vertexToName) {
            status->SetError(1);
            return;
        }*/
    
        m_AdjacencyMatrix.Unset(GetVertexNumber(vertexFromName), GetVertexNumber(vertexToName), status);

        if(status->m_Error) {
            status->SetError(12);
            return;
        }

        status->SetSuccess();
    }

    template<class T, class W> void Graph<T, W>::RemoveVertex(T vertexName, TL::Status *status)
    {
        status->SetNull();

        if(!IsExisted(vertexName)) {
            status->SetError(7);
            return;
        }

        unsigned int vertexNumber = GetVertexNumber(vertexName);

        m_AdjacencyMatrix.RemoveRow(vertexNumber, status);

        if(status->m_Error) {
            status->SetError(13);
            return;
        }

        if(m_VertexNames.Size() != 1) { // если матрица 1*1, значит RemoveRow() полностью удаляет матрицу, и тогда удалять столбец не нужно
            m_AdjacencyMatrix.RemoveColumn(vertexNumber, status);

            if(status->m_Error) {
                status->SetError(13);
                return;
            }
        }

        m_VertexNames.Remove(vertexNumber, status);

        if(status->m_Error) {
            status->SetError(15);
            return;
        }

        status->SetSuccess();
    }

    template<class T, class W> unsigned int Graph<T, W>::Size()
    {
        return m_AdjacencyMatrix.GetRowCount();
    }

    template<class T, class W> std::ostream &operator<<(std::ostream &stream, Graph<T, W> &graph)
    {
        TL::Vector<unsigned int> edges;
        TL::Status status;

        for(unsigned int i = 0; i < graph.Size(); i++) {
            stream << "Vertex \"" << graph.m_VertexNames[i] << "\"";

            edges = graph.GetEdges(graph.m_VertexNames[i], &status);

            if(status.m_Error) {
                stream << "\nERROR!!!\n";
                return stream;
            }

            // TODO: уменьшить код
            if(edges.Size() > 1) {
                stream << " has " << edges.Size() << " edges: ";

                for(unsigned int j = 0; j < edges.Size(); j++) {
                    stream << "[ \"" << graph.m_VertexNames[edges[j]] << "\" - W="
                        << graph.m_AdjacencyMatrix.Get(i, edges[j], &status) << " ]   ";

                    if(status.m_Error) {
                        stream << "\nERROR!!!\n";
                        return stream;
                    }
                }

                stream << "\n";
            } else if(edges.Size() == 1) {
                stream << " has 1 edge: "
                    << "[ \"" << graph.m_VertexNames[edges[0]] << "\" - W="
                    << graph.m_AdjacencyMatrix.Get(i, edges[0], &status) << " ]\n";

                if(status.m_Error) {
                    stream << "\nERROR!!!\n";
                    return stream;
                }
            } else {
                stream << " has no edges\n";
            }
        }

        return stream;
    }
}

#endif